"""
OPC UA协议驱动
支持OPC UA客户端连接和数据读写
基于asyncua库实现
"""

import asyncio
import logging
import threading
from datetime import datetime
from typing import Dict, Any, List, Optional

from core.base_driver import BaseDriver, TagValue

try:
    from asyncua import Client, ua
    from asyncua.common.node import Node
    OPCUA_AVAILABLE = True
except ImportError:
    OPCUA_AVAILABLE = False

class OpcuaDriver(BaseDriver):
    """OPC UA协议驱动类"""
    
    def __init__(self, config: Dict[str, Any]):
        super().__init__(config)
        
        if not OPCUA_AVAILABLE:
            raise ImportError("asyncua库未安装，请运行: pip install asyncua")
        
        # OPC UA连接参数
        self.server_url = config.get('config', {}).get('endpoint') or config.get('config', {}).get('server_url') or config.get('server_url', 'opc.tcp://localhost:4840')
        self.username = config.get('config', {}).get('username') or config.get('username')
        self.password = config.get('config', {}).get('password') or config.get('password')
        self.security_policy = config.get('config', {}).get('security_policy') or config.get('security_policy', 'None')
        self.security_mode = config.get('config', {}).get('security_mode') or config.get('security_mode', 'None')
        self.certificate_path = config.get('config', {}).get('certificate') or config.get('certificate_path')
        self.private_key_path = config.get('config', {}).get('private_key') or config.get('private_key_path')
        
        # 连接超时和重试
        self.timeout = config.get('timeout', 30)
        self.request_timeout = config.get('request_timeout', 10)
        
        # OPC UA客户端
        self.client = None
        self.event_loop = None
        self.loop_thread = None
        
        # 节点缓存
        self.node_cache = {}
        
        self.logger.info(f"OPC UA驱动初始化完成: {self.server_url}")
    
    def connect(self) -> bool:
        """建立OPC UA连接"""
        try:
            # 创建事件循环
            if self.event_loop is None:
                self.event_loop = asyncio.new_event_loop()
                self.loop_thread = threading.Thread(
                    target=self._run_event_loop,
                    daemon=True
                )
                self.loop_thread.start()
            
            # 在事件循环中执行连接
            future = asyncio.run_coroutine_threadsafe(
                self._async_connect(),
                self.event_loop
            )
            
            result = future.result(timeout=self.timeout)
            
            if result:
                self.connected = True
                self.last_error = None
                self.logger.info(f"OPC UA连接成功: {self.server_url}")
                return True
            else:
                self.connected = False
                return False
                
        except Exception as e:
            self.connected = False
            self.last_error = str(e)
            self.logger.error(f"OPC UA连接失败: {e}")
            return False
    
    def disconnect(self):
        """断开OPC UA连接"""
        try:
            if self.client and self.event_loop:
                future = asyncio.run_coroutine_threadsafe(
                    self._async_disconnect(),
                    self.event_loop
                )
                future.result(timeout=10)
            
            self.connected = False
            self.logger.info("OPC UA连接已断开")
            
        except Exception as e:
            self.logger.error(f"OPC UA断开连接失败: {e}")
    
    def read_tags(self, tag_names: List[str]) -> List[TagValue]:
        """批量读取标签"""
        if not self.connected or not self.event_loop:
            return []
        
        # 将标签名称转换为标签配置
        tag_configs = []
        for tag_name in tag_names:
            # 从self.data_points中找到对应的配置
            if tag_name in self.data_points:
                data_point = self.data_points[tag_name]
                # 转换为OPC UA所需的配置格式
                tag_config = {
                    'name': data_point.name,
                    'description': data_point.description,
                    'node_id': data_point.address,  # address字段存储node_id
                    'data_type': data_point.data_type.value,
                    'access': data_point.access,
                    'unit': data_point.unit,
                    'scaling': data_point.scaling
                }
                tag_configs.append(tag_config)
            else:
                self.logger.warning(f"未找到标签 {tag_name} 的配置")
        
        if not tag_configs:
            return []
        
        try:
            future = asyncio.run_coroutine_threadsafe(
                self._async_read_tags(tag_configs),
                self.event_loop
            )
            
            tag_values = future.result(timeout=self.request_timeout)
            return tag_values
            
        except Exception as e:
            self.logger.error(f"读取标签失败: {e}")
            return []
    
    def write_tag(self, tag_name: str, value: Any) -> bool:
        """写入单个标签"""
        if not self.connected or not self.event_loop:
            return False
        
        # 从data_points中找到标签信息
        if tag_name not in self.data_points:
            self.logger.error(f"未找到标签配置: {tag_name}")
            return False
        
        data_point = self.data_points[tag_name]
        # 转换为OPC UA所需的配置格式
        tag_config = {
            'name': data_point.name,
            'description': data_point.description,
            'node_id': data_point.address,  # address字段存储node_id
            'data_type': data_point.data_type.value,
            'access': data_point.access,
            'unit': data_point.unit,
            'scaling': data_point.scaling
        }
        
        try:
            future = asyncio.run_coroutine_threadsafe(
                self._async_write_tag(tag_config, value),
                self.event_loop
            )
            
            result = future.result(timeout=self.request_timeout)
            return result
            
        except Exception as e:
            self.logger.error(f"写入标签 {tag_name} 失败: {e}")
            return False
    
    def _run_event_loop(self):
        """运行事件循环线程"""
        asyncio.set_event_loop(self.event_loop)
        self.event_loop.run_forever()
    
    async def _async_connect(self) -> bool:
        """异步连接OPC UA服务器"""
        try:
            self.client = Client(url=self.server_url)
            
            # 设置会话超时
            self.client.session_timeout = self.timeout * 1000  # 毫秒
            
            # 设置安全策略
            if self.security_policy != 'None':
                # 这里可以添加安全策略配置
                pass
            
            # 设置认证
            if self.username and self.password:
                self.client.set_user(self.username)
                self.client.set_password(self.password)
            
            # 连接服务器
            await self.client.connect()
            
            # 获取根节点
            root = self.client.get_root_node()
            
            # 浏览服务器信息
            server_info = await self.client.get_server_node().read_browse_name()
            self.logger.info(f"连接到OPC UA服务器: {server_info}")
            
            return True
            
        except Exception as e:
            self.logger.error(f"OPC UA异步连接失败: {e}")
            return False
    
    async def _async_disconnect(self):
        """异步断开OPC UA连接"""
        try:
            if self.client:
                await self.client.disconnect()
                self.client = None
        except Exception as e:
            self.logger.error(f"OPC UA异步断开失败: {e}")
    
    async def _async_read_tags(self, tags: List[Dict[str, Any]]) -> List[TagValue]:
        """异步读取标签"""
        tag_values = []
        
        if not self.client:
            return tag_values
        
        for tag in tags:
            try:
                # 获取节点
                node = await self._get_node(tag)
                if node is None:
                    # 创建错误标签值
                    tag_value = TagValue(
                        device_name=self.device_name,
                        tag_name=tag['name'],
                        value=None,
                        timestamp=datetime.now(),
                        quality="BAD"
                    )
                    tag_values.append(tag_value)
                    continue
                
                # 读取值
                data_value = await node.read_data_value()
                
                if data_value.StatusCode.is_good():
                    value = data_value.Value.Value
                    
                    # 应用缩放和转换
                    scaled_value = value
                    scaling = tag.get('scaling')
                    if scaling:
                        try:
                            raw_low = scaling.get('raw_low', 0)
                            raw_high = scaling.get('raw_high', 1)
                            eng_low = scaling.get('eng_low', 0)
                            eng_high = scaling.get('eng_high', 1)
                            
                            # 线性量程转换
                            ratio = (value - raw_low) / (raw_high - raw_low)
                            scaled_value = eng_low + ratio * (eng_high - eng_low)
                        except Exception as e:
                            self.logger.warning(f"量程转换失败: {e}，使用原始值")
                            scaled_value = value
                    
                    tag_value = TagValue(
                        device_name=self.device_name,
                        tag_name=tag['name'],
                        value=scaled_value,
                        timestamp=data_value.SourceTimestamp or datetime.now(),
                        quality="GOOD"
                    )
                else:
                    # 状态码不好
                    tag_value = TagValue(
                        device_name=self.device_name,
                        tag_name=tag['name'],
                        value=None,
                        timestamp=datetime.now(),
                        quality="BAD"
                    )
                
                tag_values.append(tag_value)
                
            except Exception as e:
                self.logger.error(f"读取标签 {tag['name']} 失败: {e}")
                tag_value = TagValue(
                    device_name=self.device_name,
                    tag_name=tag['name'],
                    value=None,
                    timestamp=datetime.now(),
                    quality="BAD"
                )
                tag_values.append(tag_value)
        
        return tag_values
    
    async def _async_write_tag(self, tag_config: Dict[str, Any], value: Any) -> bool:
        """异步写入标签"""
        try:
            if not self.client:
                return False
            
            # 获取节点
            node = await self._get_node(tag_config)
            if node is None:
                return False
            
            # 转换数据类型
            converted_value = self._convert_value_type(value, tag_config)
            
            # 写入值
            await node.write_value(converted_value)
            
            self.logger.debug(f"写入标签 {tag_config['name']} = {converted_value}")
            return True
            
        except Exception as e:
            self.logger.error(f"写入标签 {tag_config['name']} 失败: {e}")
            return False
    
    async def _get_node(self, tag_config: Dict[str, Any]) -> Optional[Node]:
        """获取OPC UA节点"""
        try:
            if not self.client:
                return None
            
            node_id = tag_config.get('node_id')
            if not node_id:
                self.logger.error(f"标签 {tag_config['name']} 缺少node_id")
                return None
            
            # 检查缓存
            if node_id in self.node_cache:
                return self.node_cache[node_id]
            
            # 创建节点
            if isinstance(node_id, str):
                # 字符串节点ID
                node = self.client.get_node(node_id)
            elif isinstance(node_id, int):
                # 数字节点ID
                node = self.client.get_node(f"ns=2;i={node_id}")
            else:
                # 其他格式
                node = self.client.get_node(str(node_id))
            
            # 验证节点存在
            await node.read_browse_name()
            
            # 缓存节点
            self.node_cache[node_id] = node
            
            return node
            
        except Exception as e:
            self.logger.error(f"获取节点失败 {tag_config['name']}: {e}")
            return None
    
    def _convert_value_type(self, value: Any, tag_config: Dict[str, Any]) -> Any:
        """转换值的数据类型"""
        try:
            data_type = tag_config.get('data_type', 'string').lower()
            
            if data_type == 'bool':
                return bool(value)
            elif data_type == 'int':
                return int(value)
            elif data_type == 'float':
                return float(value)
            elif data_type == 'double':
                return float(value)
            elif data_type == 'string':
                return str(value)
            else:
                return value
                
        except Exception as e:
            self.logger.error(f"数据类型转换失败: {e}")
            return value
    
    def get_device_info(self) -> Dict[str, Any]:
        """获取设备信息"""
        info = super().get_device_info()
        
        info.update({
            'server_url': self.server_url,
            'security_policy': self.security_policy,
            'security_mode': self.security_mode,
            'protocol': 'OPC UA',
            'timeout': self.timeout,
            'cached_nodes': len(self.node_cache)
        })
        
        return info
    
    def clear_cache(self):
        """清空节点缓存"""
        self.node_cache.clear()
        self.logger.info("OPC UA节点缓存已清空")
    
    def __del__(self):
        """析构函数"""
        try:
            if self.event_loop and self.event_loop.is_running():
                self.event_loop.call_soon_threadsafe(self.event_loop.stop)
        except:
            pass 